/*
  ANDRE PETRIS ESTEVE
  RA 070168

  READ ME:

  O co'digo foi iniciado antes da mudanca do enunciado no sussy e verificava se o three-handshake do tcp
  tinha sido executado corretamente antes de contabilizar a sessao. Por favor, ignore as estruturas e 
  funcoes nao utilizadas, elas eram utilizadas para validar o handshake.
*/

/*
#define DEBUG
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <net/ethernet.h>
#include <netinet/ether.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pcap.h>

/*
  BEGIN Data tyes
 */

struct session_t
{
   struct in_addr sip;
   struct in_addr dip;
   uint16_t sport;
   uint16_t dport;
  
   struct session_t* next;
   struct session_t* prev;
};

struct protodata_t
{
   uint16_t port;
   unsigned int nsession;
};

struct param_t
{
   struct in_addr ip;
   struct ether_addr mac;
   struct protodata_t* proto;
   struct session_t* slist;

   int proto_n;
   pcap_t* f;

   unsigned int p_udp;
   unsigned int p_tcp;
   unsigned int s_tcp;
};

struct proto_t
{
   char* name;
   uint16_t port;
};

/*
  END Data tyes
 */

/*
  BEGIN Gobals
 */

char errbuf[PCAP_ERRBUF_SIZE];

/*
  END Globals
 */

/*
  BEGIN Code
 */

/*
  Prints program usage.
 */
void printusage()
{
   printf("usage:\nextractor --victim-ip ip_val --victim-ethernet mac_val [-proto proto0,proto1] dump.pcap\n");
}

int proto_cmp(const void* a, const void* b)
{
   struct proto_t *pa, *pb;
   pa = (struct proto_t*)a;
   pb = (struct proto_t*)b;

   return strcmp(pa->name, pb->name);
}

int protodata_cmp(const void* a, const void* b)
{
   struct protodata_t *pa, *pb;
   pa = (struct protodata_t*)a;
   pb = (struct protodata_t*)b;

   return pa->port - pb->port;
}

void freed(struct session_t* d)
{
   struct session_t* n;

   while (d)
   {
      n = d;
      d = d->next;
      free(n);
   }
}

void freep(struct param_t* p)
{
   if (p->proto != NULL)
      free(p->proto);

   freed(p->slist);
}

void get_protos(char* protolist, struct param_t* p)
{
   char line[512];
   char name[50], port[15], alias[256], palias[50];
   char c;
   char **plist;
   int n, k, i, j, listempty, l, li;
   FILE* f;

   /* service's port list from /etc/services */
   f = fopen("services", "r");

   if (!f)
   {
      printf("Impossible to read /etc/services\n");
      free(p);
      exit(1);
   }

   n = 1;
   for (i = 0; protolist[i]; i++)
      if (protolist[i] == ',')
         n++;

   p->proto_n = n;
   p->proto = (struct protodata_t*)malloc(sizeof(struct protodata_t)*n);
   plist = (char**)malloc(sizeof(char*)*n);

   n = 0;
   l = 0;
   for (i = 0; protolist[i]; i++)
   {  
      if (protolist[i] == ',')
      {
         protolist[i] = '\0';
         plist[n++] = &protolist[l];
         l = i+1;
      }
   }

   plist[n++] = &protolist[l];

   while (!feof(f))
   {
      fgets(line, 512, f);

      if (line[0] == '#' || line[0] == '\n')
         continue;        

      k = sscanf(line, "%s%s %256c", name, port, alias);

      i = 0; 
      while (port[i] != '/') i++;
      port[i] = '\0';      

      listempty = 1;

      for (i = 0; i < n; i++)
      {
         if (!plist[i][0])
            continue;

         listempty = 0;

         if (!strcmp(name, plist[i]))
         {
            /* we found proto! */
            p->proto[i].port = htons((uint16_t)atoi(port));            
            p->proto[i].nsession = 0;
            plist[i][0] = '\0';

#ifdef DEBUG
            printf("PROTO: %s:%d\n", pr->name, pr->port);
#endif

            continue;
         }
         /* we didn't find proto yet, let's look the alias */

         if (k == 3 && alias[0] != '#')
         {
            li = 0;

            for (j = 0; alias[j] != '#'; j++)
            {      
               c = alias[j];

               if (c == ' ' || c == '\n')
               {
                  /* palias is an alias name now */
                  strncpy(palias, &alias[li], j - li);
                  palias[j - li + 1] = '\0';

                  if (!strcmp(plist[i], palias))
                  {
                     /* we found proto! */
                     p->proto[i].port = htons((uint16_t)atoi(port));
                     p->proto[i].nsession = 0;
                     plist[i][0] = '\0';

#ifdef DEBUG
                     printf("PROTO: %s:%d\n", pr->name, pr->port);
#endif

                     break;
                  }

                  li = j+1;

                  if (c == '\n')
                     break;
               } /* end if c */
            } /* end for */
         } /* end if n */
      } /* end for poto */

      /* all protos found */
      if (listempty)
         break;

   } /* end for file */

   fclose(f);

   if (!listempty)
   {
      for (i = 0; i < n; i++)
      {   
         if (plist[i][0] != '\0')
            printf("Invalid protocol %s.\n", plist[i]);
      }

      freep(p);
      free(plist);
      exit(1);
   }

   free(plist);

   qsort((void*)p->proto, p->proto_n, sizeof(struct protodata_t), protodata_cmp);
}

/*
  Analyse program parameters
*/
struct param_t get_parms(int n, char** argv)
{
   int i;
   struct param_t params;
   struct ether_addr* mac;   

   int hasip, hasmac;
   hasip = hasmac = 0;

   memset(&params, 0, sizeof(struct param_t));

   if (n < 6)
   {
      printusage();
      exit(1);
   }

   for (i = 1; i < n-1; i++)
   {
      /* get ip address */
      if (!strcmp(argv[i], "--victim-ip"))
      {
         if (!inet_aton(argv[++i], &(params.ip)))
         {
            printf("Invalid ip address\n");
            exit(1);
         }
         else
            hasip = 1;         

#ifdef DEBUG
         printf("IP: %s\n", argv[i]);
#endif

      }

      /* get mac address */
      if (!strcmp(argv[i], "--victim-ethernet"))
      {
         if ((mac = (struct ether_addr*)ether_aton(argv[++i])))
         {
            memcpy(&(params.mac), mac, sizeof(struct ether_addr));
            hasmac = 1;
         }
         else
         {
            printf("Invalid MAC address\n");
            exit(1);
         }

#ifdef DEBUG
         printf("MAC: %s\n", argv[i]);
#endif
      }

      /* get protos */
      if (!strcmp(argv[i], "-proto"))
         get_protos(argv[++i], &params);
   }

   if (hasip != 1 || hasmac != 1)
   {
      printusage();
      freep(&params);
      exit(1);
   }

   /* open capture file for offline processing */
   if (!(params.f = pcap_open_offline(argv[n-1], errbuf)))
   {
      printf("Invalid pcap file %s: %s\n", argv[n-1], errbuf);
      freep(&params);
      exit(1);
   }

   params.slist = (struct session_t*)malloc(sizeof(struct session_t));
   params.slist->prev = params.slist->next = NULL;

   return params;
}

/*
  Compares two ethernet addresses
  Returns 1 on equal addresses 
 */
int ether_cmp(const struct ether_addr* a, const struct ether_addr* b)
{
   int i;
  
   for (i = 0; i < ETH_ALEN; i++)
      if (a->ether_addr_octet[i] != b->ether_addr_octet[i])
         return 0;

   return 1;
}

void append(struct session_t *d, struct session_t* new)
{
   while(d->next)
      d = d->next;

   d->next = new;
   new->prev = d;
}


void remove_session(struct session_t* s)
{
   struct session_t* t;

   if ((t = s->prev))
      t->next = s->next;

   if ((t = s->next))
      t->prev = s->prev;
   
   free(s);
}


/* returns session_t on session list equals s or NULL otherwise */
struct session_t* contains(struct session_t* slist, const struct session_t* s)
{
   while (slist)
   {
      if (slist->sip.s_addr == s->sip.s_addr && slist->dip.s_addr == s->dip.s_addr
          && slist->sport == s->sport && slist->dport == s->dport)
         return slist;

      slist = slist->next;
   }

   return NULL; 
}

/* inverts a session tuple */
void invert_session(struct session_t* s)
{
   uint16_t tmp16;
   uint32_t tmp32;

   tmp16 = s->sport;
   s->sport = s->dport;
   s->dport = tmp16;
  
   tmp32 = s->sip.s_addr;
   s->sip.s_addr = s->dip.s_addr;
   s->dip.s_addr = tmp32;
}

void new_session(struct session_t* slist, const struct session_t* s, struct param_t* p)
{
/*
   struct session_t* d;
*/
   struct protodata_t *proto;
   int i;
   
#ifdef DEBUG
   printf("New session\nsport: %u\tdport: %u\n-----\n", ntohs(s->sport),
          ntohs(s->dport));
#endif

/*
  
   * gets or new session node  *\/
   d = (struct session_t*)malloc(sizeof(struct session_t));
   memcpy(d, s, sizeof(struct session_t));
   d->next = NULL;
   d->prev = NULL;
   append(slist, d);
*/ 
   /*counts tcp session*/
   p->s_tcp++;

   for (i = 0; i < p->proto_n; i++)
   {
      proto = &p->proto[i];

      if (proto->port == s->sport || proto->port == s->dport)
         proto->nsession++;
   }
}

void end_session(struct session_t* slist, struct session_t* s, struct param_t* p)
{

#ifdef DEBUG
   printf("End session\nsport: %u\tdport: %u\n-----\n", ntohs(s->sport),
          ntohs(s->dport));
#endif

   remove_session(s);
}

void analyse_pkt(struct session_t* slist, const struct session_t* s, const struct tcphdr* tcp, struct param_t* p)
{
   /*flag SYN and !AKC for new session */
   if (tcp->syn == 1 && tcp->ack == 0)
   {
      new_session(slist, s, p);
   }
/*
   ----THREEHANDSHAKE

   struct session_t sc;
   struct session_t* d;

   d = contains(slist, s);

   * if there's no session with the pkt we have
    may there is some with its converse 
    (which, in essence, is the same session) *\/
   if (!d)
   {
      memcpy(&sc, s, sizeof(struct session_t));
      invert_session(&sc);
      d = contains(slist, &sc);
   }

   *flag SYN for new session and session not yet started*\/
   if (tcp->syn == 1 && !d)
   {
      new_session(slist, s, p);
   }
   * if request for end session *\/
   else if (d && (tcp->fin || tcp->rst))
   {
      end_session(slist, d, p);
   }
*/
}

void packet_handler(u_char *user, const struct pcap_pkthdr *h,
                    const u_char *packet)
{
   struct param_t* p;
   const struct ether_header* etheader;
   const struct ip* ipheader;
   const struct tcphdr* tcpheader;
   const struct udphdr* udpheader;
   struct session_t s;
   u_int sourceport, destport;

   p = (struct param_t*)user;

   etheader = (struct ether_header*)packet;

#ifdef DEBUG

   printf("EhterFrame - src: %s - dest: %s\n", 
          ether_ntoa((const struct ether_addr*)&etheader->ether_shost), 
          ether_ntoa((const struct ether_addr*)&etheader->ether_dhost));

#endif

   /* if it's not our mac, ignore this frame */
   if (!ether_cmp((const struct ether_addr*)&etheader->ether_dhost, &p->mac) &&
       !ether_cmp((const struct ether_addr*)&etheader->ether_shost, &p->mac))
      return;
   
   /* if it's not IP, ignore it */
   if (ntohs(etheader->ether_type) != ETHERTYPE_IP) 
      return;
   
   ipheader = (struct ip*)(packet + sizeof(struct ether_header));
  
#ifdef DEBUG

   printf("IPDgram - src: %s - dest: %s\n", 
          inet_ntoa(ipheader->ip_src), 
          inet_ntoa(ipheader->ip_dst));

#endif
 
   /* if it's not our ip the src or dest, ignore it */
   if (ipheader->ip_src.s_addr != p->ip.s_addr && ipheader->ip_dst.s_addr != p->ip.s_addr)
      return;
 
   if (ipheader->ip_p == IPPROTO_TCP) 
   {
      tcpheader = (const struct tcphdr*)(packet + sizeof(struct ether_header) + sizeof(struct ip));
      sourceport = ntohs(tcpheader->source);
      destport = ntohs(tcpheader->dest);

      p->p_tcp++;

      s.sip.s_addr = ipheader->ip_src.s_addr;
      s.dip.s_addr = ipheader->ip_dst.s_addr;
      s.sport = tcpheader->source;
      s.dport = tcpheader->dest;
   
      analyse_pkt(p->slist, &s, tcpheader, p);
   }
   else if (ipheader->ip_p == IPPROTO_UDP)
   {
      udpheader = (const struct udphdr*)(packet + sizeof(struct ether_header) + sizeof(struct ip));
      sourceport = ntohs(udpheader->source);
      destport = ntohs(udpheader->dest);
      
      p->p_udp++;
   }
  
}


void job(struct param_t* p)
{
   int i;

   if (pcap_loop(p->f, 0, packet_handler, (u_char*)p) < 0)
      printf("pcap_loop failed: %s\n", pcap_geterr(p->f));

   printf("%u\n%u\n%u\n", p->p_tcp, p->p_udp,
          p->s_tcp);

   for (i = 0; i < p->proto_n; i++)
   {
#ifdef DEBUG
      printf("port %u: ", ntohs(p->proto[i].port));
#endif
      printf("%u\n", p->proto[i].nsession);
   }
}

int main(int argn, char** argv)
{
   struct param_t p;

   p = get_parms(argn, argv);   

   job(&p);

   pcap_close(p.f);
   freep(&p);

   return 0;
}
